/*
 * JGO2RDF creates a RDF representation of GAF2 files, which are automatically sourced from GO ftp site.
 * Copyright (C) 2012 Matthew M. Hindle & Artem Lysenko
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package uk.co.jgo2rdf.goa.go;

import org.apache.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;


/**
 * Singleton classifies if a Gene Ontology term is specific to a given gene or gene product class based on a properties file and inheritance information from GO
 */
public class GOClassifier {

    private static GOClassifier INSTANCE;
    private static final Logger log = Logger.getLogger(GOClassifier.class);
    private GOIndexerLite go_index;

    public static synchronized GOClassifier getInstance() throws Exception {

        if (INSTANCE != null)
            return INSTANCE;

        synchronized (GOClassifier.class) {
            if (INSTANCE != null)
                return INSTANCE;
            INSTANCE = new GOClassifier();
        }
        return INSTANCE;
    }


    public static enum GOFunctionalTargetType {
        DNA, RNA, PROTEIN, NONE;

        public static final Map<String, GOFunctionalTargetType> translations = new HashMap<String, GOFunctionalTargetType>();

        static {
            translations.put("dna", DNA);
            translations.put("rna", RNA);
            translations.put("protein", PROTEIN);
            translations.put("d", DNA);
            translations.put("r", RNA);
            translations.put("p", PROTEIN);
        }

        public static GOFunctionalTargetType ofType(String type) throws UnknownFunctionalTargetTypeException {
            GOFunctionalTargetType funcType = translations.get(type.toLowerCase());
            if (funcType == null) {
                throw new UnknownFunctionalTargetTypeException(type);
            }
            return funcType;
        }
    }

    private GOClassifier() throws IOException, OntologyType.UnknownOntologyNameException, MalformedPropertiesFileException, UnknownFunctionalTargetTypeException, SQLException {
        this(GOClassifier.class.getResourceAsStream("/GOTermFunctionalTargets.properties"));
    }

    private GOClassifier(InputStream propertiesStream) throws IOException, MalformedPropertiesFileException, UnknownFunctionalTargetTypeException, SQLException {   //main constructor
        try {
            go_index = new GOIndexerLite(GOClassifier.class.getResourceAsStream("/gene_ontology_ext.obo"));
        } catch (OntologyType.UnknownOntologyNameException e) {
            log.error(e);
            e.printStackTrace();
        }
        Properties prop = new Properties();
        prop.load(propertiesStream);
        loadIndex(prop);
        log.info("index complete");
    }

    public GOIndexerLite getGo_index() {
        return go_index;
    }

    private final Map<String, GOFunctionalTargetType> map = new HashMap<String, GOFunctionalTargetType>();

    private void loadIndex(Properties classifications) throws UnknownFunctionalTargetTypeException {
        log.info("building index of target type specific GO terms");

        for (Map.Entry<Object, Object> entry : classifications.entrySet()) {
            String goid = "GO:" + entry.getKey().toString();
            String category = (String) entry.getValue();
            GOFunctionalTargetType targetType = GOFunctionalTargetType.ofType(category);
            if (map.containsKey(goid))
                log.error("GO terms can only be bound to one target type");
            map.put(goid, targetType);

            String realId = go_index.getRealId(goid);
            if (realId != null) goid = realId;

            for (String term : go_index.getChildren(goid)) {
                if (map.containsKey(term))
                    log.error("GO terms can only be bound to one target type: clash because of inheritance");
                map.put(term, targetType);
                System.out.println("added " + term);
            }
        }

    }

    public boolean hasFunctionalCategory(String goterm) {
        String realId = go_index.getRealId(goterm);
        if (realId != null) goterm = realId;
        return map.get(goterm) != null;
    }

    public GOFunctionalTargetType getFunctionalCategory(String goterm) throws FunctionalCategoryNotSpecifiedException {
        String realId = go_index.getRealId(goterm);
        if (realId != null) goterm = realId;
        GOFunctionalTargetType targetType = map.get(goterm);
        if (targetType == null)
            throw new FunctionalCategoryNotSpecifiedException("Classification not specified for " + goterm);
        return targetType;
    }

    public static class FunctionalCategoryNotSpecifiedException extends MalformedPropertiesFileException {

        public FunctionalCategoryNotSpecifiedException(String message) {
            super(message);
        }
    }

    public static class DuplicateGOIDException extends MalformedPropertiesFileException { //I changed this to a log?? not sure

        public DuplicateGOIDException(String message) {
            super(message);
        }
    }

    public static class UnknownFunctionalTargetTypeException extends Exception {

        public UnknownFunctionalTargetTypeException(String message) {
            super(message);
        }
    }

    public static class MalformedPropertiesFileException extends Exception {

        public MalformedPropertiesFileException(String message) {
            super(message);
        }
    }

}
